﻿#include "stdafx.h"
#include "AngelScriptBinder.h"
#include "..\3rdLib\ASadd_on\scriptbuilder.h"
#include "..\3rdLib\ASadd_on\scriptstring.h"

#include "DebugAssert.h"

_NAMESPACE_BEGIN

AngelScriptBinder::AngelScriptBinder()
{
	int r = 0;

	SAVE_LOG("脚本引擎初始化");

	engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);

	//asEP_ALLOW_MULTILINE_STRINGS
	engine->SetEngineProperty(asEP_STRING_ENCODING,0);

	if( engine == 0 )
	{
		SAVE_LOG("脚本引擎创建失败");
		return;
	}

	r=engine->SetMessageCallback(asMETHOD(AngelScriptBinder,MessageCallback),this, asCALL_THISCALL);	assert(r>=0);
	r=engine->RegisterGlobalFunction("void pause()",asFUNCTION(AngelScriptBinder::pause),asCALL_CDECL);	 assert( r >= 0 );
	r=engine->RegisterGlobalFunction("void assert(bool)",asFUNCTION(AngelScriptBinder::AsAssert),asCALL_CDECL);	 assert( r >= 0 );
}

void AngelScriptBinder::MessageCallback(const asSMessageInfo *msg) 
{ 
	const char *msgType = 0;
	if( msg->type == 0 ) msgType = "Error  ";
	if( msg->type == 1 ) msgType = "Warning";
	if( msg->type == 2 ) msgType = "Info   ";

	printf("%s (%d, %d) : %s : %s\n", msg->section, msg->row, msg->col, msgType, msg->message);
	// 		SAVE_LOG(msg->section);
	// 		SAVE_LOG(msgType);
	// 		SAVE_LOG(msg->message);
}

void AngelScriptBinder::print(const char *format, ...)
{
	char buf[256];
	va_list args;
	va_start(args, format);
	vsprintf(buf, format, args);
	va_end(args);

	cout<<buf;
}

void AngelScriptBinder::PrintVariables(asIScriptContext *ctx, int stackLevel)
{
	asIScriptEngine *engine = ctx->GetEngine();

	int typeId = ctx->GetThisTypeId(stackLevel);
	void *varPointer = ctx->GetThisPointer(stackLevel);
	if( typeId )
	{
		print(" this = 0x%x\n", varPointer);
	}

	int numVars = ctx->GetVarCount(stackLevel);
	for( int n = 0; n < numVars; n++ )
	{
		int typeId = ctx->GetVarTypeId(n, stackLevel); 
		void *varPointer = ctx->GetAddressOfVar(n, stackLevel);
		if( typeId == engine->GetTypeIdByDecl("int") )
		{
			print(" %s = %d\n", ctx->GetVarDeclaration(n, stackLevel), *(int*)varPointer);
		}
		else if( typeId == engine->GetTypeIdByDecl("string") )
		{
			CScriptString *str = (CScriptString*)varPointer;
			if( str )
				print(" %s = '%s'\n", ctx->GetVarDeclaration(n, stackLevel), str->buffer.c_str());
			else
				print(" %s = <null>\n", ctx->GetVarDeclaration(n, stackLevel));
		}
	}
}

void AngelScriptBinder::BindAllImportedFunctions( CONST_REF(string) module )
{
	int r = engine->GetModule(module.c_str())->BindAllImportedFunctions(); assert( r >= 0 );
}

void AngelScriptBinder::ExceptionCallback(asIScriptContext *ctx)
{
	asIScriptEngine *engine = ctx->GetEngine();
	int funcID = ctx->GetExceptionFunction();
	const asIScriptFunction *function = engine->GetFunctionDescriptorById(funcID);
	print("--- exception ---\n");
	print("desc: %s\n", ctx->GetExceptionString());
	print("func: %s\n", function->GetDeclaration());
	print("modl: %s\n", function->GetModuleName());
	print("sect: %s\n", function->GetScriptSectionName());
	int col, line = ctx->GetExceptionLineNumber(&col);
	print("line: %d,%d\n", line, col);

	// Print the variables in the current function
	PrintVariables(ctx, -1);

	// Show the call stack with the variables
	print("--- call stack ---\n");
	for( int n = 0; n < ctx->GetCallstackSize(); n++ )
	{
		funcID = ctx->GetCallstackFunction(n);
		const asIScriptFunction *func = engine->GetFunctionDescriptorById(funcID);
		line = ctx->GetCallstackLineNumber(n,&col);
		print("%s:%s:%d,%d\n", func->GetModuleName(),
			func->GetDeclaration(),
			line, col);
		PrintVariables(ctx, n);
	}
}


int AngelScriptBinder::CompileMainScript( const char* file )
{
	int r = CompileScript("",file);
	if( r < 0 )
	{
		engine->Release();
		return -1;
	}

	mainctx = engine->CreateContext();
	if( mainctx == 0 ) 
	{
		cout << "Failed to create the context." << endl;
		engine->Release();
		return -1;
	}		

	r = mainctx->SetExceptionCallback(asMETHOD(AngelScriptBinder,ExceptionCallback),this, asCALL_THISCALL);	assert(r>=0);

	contexts.push_back(mainctx);

	return 0;
}

int AngelScriptBinder::CompileScript(CONST_REF(string) module,CONST_REF(string) file)
{
	int r;
	CScriptBuilder builder;
	if(module.empty())
		r = builder.BuildScriptFromFile(engine,NULL, file.c_str());
	else
		r = builder.BuildScriptFromFile(engine,module.c_str(), file.c_str());

	////保存已编译的脚本到文件
	//CBytecodeStream stream("CompiledScript");
	//asIScriptModule * mod = engine->GetModule(0);
	//mod->SaveByteCode(&stream);
	//
	//asBYTE* bin = NULL;
	//bin = new asBYTE[stream.buffer.size()];
	//
	//UINT count = 0;
	//for (std::vector<asBYTE>::iterator i = stream.buffer.begin();
	//	i != stream.buffer.end(); ++i)
	//{
	//	bin[count] = *i;
	//	count++;
	//}

	//FILE* fp = fopen("ScriptBin.bin","wb+");
	//assert(fp);

	//size_t size = stream.buffer.size();
	//fwrite(&size,sizeof(size_t),1,fp);
	//fwrite(bin,sizeof(asBYTE),size,fp);
	//
	//fclose(fp);
	//delete[] bin;

	string name="ScriptBin";
	SAVE_LOG("编译脚本到文件: ");
	SAVE_LOG(name+".bin");

	CBytecodeStream stream(name.c_str());

	asIScriptModule *mod = NULL;
	if(module.empty())
	{
		mod = engine->GetModule(0);
	}
	else
	{
		mod = engine->GetModule(module.c_str());
	}


	mod->SaveByteCode(&stream);

	if( r < 0 )
	{
		SAVE_LOG("编译失败");
		return r;
	}

	return 0;
}

int AngelScriptBinder::LoadCompiledScript( CONST_REF(string) module,CONST_REF(string) file )
{
// 	FILE* fp = fopen("ScriptBin.bin","rb");
// 	assert(fp);
// 
// 	size_t size = 0;
// 	fread(&size,sizeof(size_t),1,fp);
// 
// 	asBYTE* bin = NULL;
// 	bin = new asBYTE[size];
// 
// 	fread(bin,sizeof(asBYTE),size,fp);
// 
// 	CBytecodeStream stream("CompiledScript");
// 	stream.Write((void*)bin,size);
// 
// 	asIScriptModule* mod = engine->GetModule(0, asGM_ALWAYS_CREATE);
// 
// 	fclose(fp);
// 	delete[] bin;
// 
// 	return (mod->LoadByteCode(&stream));

	string name="ScriptBin";

	SAVE_LOG("从文件载入已编译脚本: ");
	SAVE_LOG(name+".bin");

	CBytecodeStream stream(name.c_str());
	
	asIScriptModule *mod = NULL;
	if(module.empty())
	{
		mod = engine->GetModule(0, asGM_ALWAYS_CREATE);
	}
	else
	{
		mod = engine->GetModule(module.c_str(), asGM_ALWAYS_CREATE);
	}

	int res = mod->LoadByteCode(&stream);

	mainctx = engine->CreateContext();
	if( mainctx == 0 ) 
	{
		cout << "Failed to create the context." << endl;
		engine->Release();
		return -1;
	}		

	int r = mainctx->SetExceptionCallback(asMETHOD(AngelScriptBinder,ExceptionCallback),this, asCALL_THISCALL);
	assert(r>=0);

	contexts.push_back(mainctx);

	return res;
}

DWORD AngelScriptBinder::RunMainFunc()
{
	int funcId = engine->GetModule(0)->GetFunctionIdByDecl("int main()");
	if( funcId < 0 )
	{
		SAVE_LOG("int main() 函数不存在");
		mainctx->Release();
		engine->Release();
		return 1;
	}

	int r = mainctx->Prepare(funcId);
	if( r < 0 ) 
	{
		SAVE_LOG("上下文准备失败");
		mainctx->Release();
		engine->Release();
		return 1;
	}

	r = mainctx->Execute();
	if( r != asEXECUTION_FINISHED )
	{
		// The execution didn't finish as we had planned. Determine why.
		if( r == asEXECUTION_ABORTED )
			cout << "The script was aborted before it could finish. Probably it timed out." << endl;
		else if( r == asEXECUTION_EXCEPTION )
		{
			SAVE_LOG("脚本存在异常");
		}
		else
			cout << "The script ended for some unforeseen reason (" << r << ")." << endl;
	}
	else
	{
		// Retrieve the return value from the context
		DWORD returnValue = mainctx->GetReturnDWord();
		string log = "脚本主函数返回： ";
		char r[3];
		log += itoa(returnValue,r,10);
		SAVE_LOG(log);
	}
	return 0;
}

asIScriptContext * AngelScriptBinder::GetVavailableContext()
{
	for (std::vector<asIScriptContext*>::iterator i=contexts.begin();i!=contexts.end();i++)
	{
		if((*i)->GetState()==asEXECUTION_FINISHED)
			return *i;
	}
	assert(contexts.size()<4);

	asIScriptContext* ctx = engine->CreateContext();
	if( !ctx ) 
	{
		assert(!"Failed to create the context.");
		engine->Release();
		return NULL;
	}
	int r = ctx->SetExceptionCallback(asMETHOD(AngelScriptBinder,ExceptionCallback),this, asCALL_THISCALL);	assert(r>=0);
	contexts.push_back(ctx);
	return ctx;
}

void AngelScriptBinder::RunScriptFunction(asIScriptObject* pthis,const char* decl,int paramnum,... )
{
	int funcId;
	if(pthis)
	{
		asIObjectType *type =pthis->GetObjectType();
		funcId = type->GetMethodIdByDecl(decl);
		assert(funcId>0);
	}		
	else
	{
		funcId = AngelScriptBinder::Instance()->GetEngine()->GetModule(0)->GetFunctionIdByDecl(decl);
	}

	asIScriptContext * ctx=AngelScriptBinder::Instance()->GetVavailableContext();

	int r = ctx->Prepare(funcId);
	if( r < 0 ) 
	{
		cout << "Failed to prepare the context." << endl;
		return;
	}
	if(pthis)
	{
		r =ctx->SetObject(pthis);
		assert( r >= 0 );
	}

	va_list args;
	va_start(args, paramnum);

	for(int i=0;i<paramnum;i++)
		r = ctx->SetArgObject(i,va_arg(args,void*));assert(r>=0);

	va_end(args);

	r =ctx->Execute();
	if( r != asEXECUTION_FINISHED )
	{
		cout <<string("Execute error:")+"decl" << endl;
	}

}

void AngelScriptBinder::pause()
{
	__asm int 3;
}

void AngelScriptBinder::AsAssert( bool b )
{
	assert(b);
}

AngelScriptBinder::~AngelScriptBinder()
{
	engine->Release();

	SAVE_LOG("脚本引擎释放完毕");
}

//////////////////////////////////////////////////////////////////////////
ASEvent::ASEvent()
{
	funcID=0;
	pcaller=NULL;
	ctx=NULL;

}
void ASEvent::SetHandler(asIScriptObject *pthis,CONST_REF(string) func )
{
	this->pcaller=pthis;
	if(pthis)
	{
		asIObjectType *type = pcaller->GetObjectType();
		this->funcID = type->GetMethodIdByName(func.c_str());	
		this->funName=func;
	}		
}

void ASEvent::Prepare()
{
	if(!pcaller) return;	
	ctx=AngelScriptBinder::Instance()->GetVavailableContext();
	int r =ctx->Prepare(funcID);assert( r >= 0 );
	ctx->SetObject(pcaller);assert( r >= 0 );
}

void ASEvent::Execute()
{
	if(!pcaller) return;		
	int r;	
	r = ctx->Execute();
	assert( r >= 0 );
}

_NAMESPACE_END